package com.hsfw.dbmodule.utils;

import com.hsfw.dbmodule.entity.Role;
import com.hsfw.dbmodule.entity.UserDetail;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.CompressionCodecs;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

@Component
public class JWTUtil {

    public static final String ROLE_REFRESH_TOKEN = "ROLE_REFRESH_TOKEN";
    private static final String CLAIM_KEY_USER_NAME = "username";
    private static final String CLAIM_KEY_ROLE_ID = "roleId";
    private static final String CLAIM_KEY_ROLE_NAMECN = "roleNameCN";

    private Map<String, String> tokenMap = new ConcurrentHashMap<>(32);

    @Value("${JWT.header}")
    private String authHeader;

    @Value("${JWT.tokenHead}")
    private String tokenHead;

    @Value("${JWT.secret}")
    private String secret;

    @Value("${JWT.expiration}")
    private Long accessTokenExpiration;

    @Value("${JWT.expiration}")
    private Long refreshTokenExpiration;

    private final SignatureAlgorithm SIGNATURE_ALGORITHM = SignatureAlgorithm.HS256;

    /**
     * 从token中获取用户实体信息
     *
     * @param token
     * @return
     */
    public UserDetail getUserFromToken(String token) {
        UserDetail userDetail;
        try {
            final Claims claims = getClaimsFromToken(token);

            String account = claims.getSubject();
            String username = claims.get(CLAIM_KEY_USER_NAME).toString();
            String roleNameCN = claims.get(CLAIM_KEY_ROLE_NAMECN).toString();
            Role role = new Role(roleNameCN);
            userDetail = new UserDetail(account,"", username,role);
        } catch (Exception e) {
            userDetail = null;
        }
        return userDetail;
    }

    /**
     *
     * @param request
     * @return
     */
    public UserDetail getUserFromToken(HttpServletRequest request) {
        String authToken = request.getHeader(authHeader);
        if (org.apache.commons.lang3.StringUtils.isNotEmpty(authToken) && authToken.startsWith(tokenHead)) {
            authToken = authToken.substring(tokenHead.length());
        }
        return getUserFromToken(authToken);
    }

    /**
     * 获取token信息
     * @param request
     * @return
     */
    public String getTokenFromRequest(HttpServletRequest request) {
        String authToken = request.getHeader(authHeader);
        if (org.apache.commons.lang3.StringUtils.isNotEmpty(authToken) && authToken.startsWith(tokenHead)) {
            authToken = authToken.substring(tokenHead.length());
        }
        return authToken;
    }

    /**
     * 从token中获取用户账户（域账户）
     *
     * @param token
     * @return
     */
    public String getUserAccountFromToken(String token) {
        String userAccount;
        try {
            final Claims claims = getClaimsFromToken(token);
            userAccount=claims.getSubject();
        } catch (Exception e) {
            userAccount = "";
        }
        return userAccount;
    }

    /**
     * 从token中获取用户姓名
     *
     * @param token
     * @return
     */
    public String getUsernameFromToken(String token) {
        String username;
        try {
            final Claims claims = getClaimsFromToken(token);
            username=claims.get(CLAIM_KEY_USER_NAME).toString();
        } catch (Exception e) {
            username = null;
        }
        return username;
    }

    public Date getCreatedDateFromToken(String token) {
        Date created;
        try {
            final Claims claims = getClaimsFromToken(token);
            created = claims.getIssuedAt();
        } catch (Exception e) {
            created = null;
        }
        return created;
    }

    /**
     * 生成token信息
     *
     * @param userDetail
     * @return
     */
    public String generateAccessToken(UserDetail userDetail) {
        Map<String, Object> claims = generateClaims(userDetail);
        return generateAccessToken(userDetail.getAccount(), claims);
    }

    public Date getExpirationDateFromToken(String token) {
        Date expiration;
        try {
            final Claims claims = getClaimsFromToken(token);
            expiration = claims.getExpiration();
        } catch (Exception e) {
            expiration = null;
        }
        return expiration;
    }

    public Boolean canTokenBeRefreshed(String token, Date lastPasswordReset) {
        final Date created = getCreatedDateFromToken(token);
        return !isCreatedBeforeLastPasswordReset(created, lastPasswordReset)
                && (!isTokenExpired(token));
    }

    public String refreshToken(String token) {
        String refreshedToken;
        try {
            final Claims claims = getClaimsFromToken(token);
            refreshedToken = generateAccessToken(claims.getSubject(), claims);
        } catch (Exception e) {
            refreshedToken = null;
        }
        return refreshedToken;
    }


    public Boolean validateToken(String token, UserDetails userDetails) {
        UserDetail userDetail = (UserDetail) userDetails;
        final String userAccount = getUserAccountFromToken(token);
        final String username = getUsernameFromToken(token);
        final Date created = getCreatedDateFromToken(token);
        return (userAccount .equals( userDetail.getAccount())
                && username.equals(userDetail.getUsername())
                && !isTokenExpired(token)
        );
    }

    public void putToken(String username, String token) {
        tokenMap.put(username, token);
    }

    public void deleteToken(String username) {
        tokenMap.remove(username);
    }

    public boolean containToken(String account, String token) {
        if (account != null && tokenMap.containsKey(account) && tokenMap.get(account).equals(token)) {
            return true;
        }
        return false;
    }

    private Claims getClaimsFromToken(String token) {
        Claims claims;
        try {
            claims = Jwts.parser()
                    .setSigningKey(secret)
                    .parseClaimsJws(token)
                    .getBody();
        } catch (Exception e) {
            claims = null;
        }
        return claims;
    }

    private Date generateExpirationDate(long expiration) {
        return new Date(System.currentTimeMillis() + expiration * 1000);
    }

    private Boolean isTokenExpired(String token) {
        final Date expiration = getExpirationDateFromToken(token);
        return expiration.before(new Date());
    }

    private Boolean isCreatedBeforeLastPasswordReset(Date created, Date lastPasswordReset) {
        return (lastPasswordReset != null && created.before(lastPasswordReset));
    }

    /**
     * 生成私有声明，保存自己想要的验证信息
     *
     * @param userDetail
     * @return
     */
    private Map<String, Object> generateClaims(UserDetail userDetail) {
        Map<String, Object> claims = new HashMap<>(16);
        claims.put(CLAIM_KEY_USER_NAME, userDetail.getUsername()==null?userDetail.getAccount():userDetail.getUsername());
        claims.put(CLAIM_KEY_ROLE_ID, userDetail.getRole().getRoleCode());
        claims.put(CLAIM_KEY_ROLE_NAMECN, authoritiesToArray(userDetail.getAuthorities()));
        return claims;
    }

    private String generateAccessToken(String subject, Map<String, Object> claims) {
        return generateToken(subject, claims, accessTokenExpiration);
    }

    private List authoritiesToArray(Collection<? extends GrantedAuthority> authorities) {
        List<String> list = new ArrayList<>();
        for (GrantedAuthority ga : authorities) {
            list.add(ga.getAuthority());
        }
        return list;
    }

    /**
     * 生成token
     * @param subject 用户账户（域账户）
     * @param claims （私有声明）
     * @param expiration （有效时间）
     * @return
     */
    private String generateToken(String subject, Map<String, Object> claims, long expiration) {
        return Jwts.builder()
                .setClaims(claims)
                .setSubject(subject)
                .setId(UUID.randomUUID().toString())
                .setIssuedAt(new Date())
                .setExpiration(generateExpirationDate(expiration))
                .compressWith(CompressionCodecs.DEFLATE)
                .signWith(SIGNATURE_ALGORITHM, secret)
                .compact();
    }

}

